

namespace Stee.CAP8.MessageBus.Sagas.Core.Sagas
{
    using Stee.CAP8.MessageBus.Interface.Models;
    using Stee.CAP8.MessageBus.Sagas.Core.Bus;
    using Stee.CAP8.MessageBus.Sagas.Core.Common;
    using Stee.CAP8.MessageBus.Sagas.Core.Storages.Nodes;
    using Stee.CAP8.MessageBus.Sagas.Core.Storages.SagaData;
    using Stee.CAP8.MessageBus.Sagas.Core.Storages.Steps;
    using System;
    using System.Collections.Concurrent;
    using System.Collections.Generic;
    using System.Reflection;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Linq;
    using Stee.CAP8.MessageBus.Sagas.Core.Attributes;
    using System.Linq.Expressions;
    using Stee.CAP8.MessageBus.Sagas.Core.Steps;
    using Stee.CAP8.MessageBus.Sagas.Core.Handlers;

    public abstract class BaseMainSaga {

        public abstract Task HandleReply(CommandMessage message);
    }


    public abstract class BaseMainSaga<TSagaData>: BaseMainSaga
        where TSagaData : BaseSagaData
    {
#pragma warning disable CA1051 // 不要声明可见实例字段
        public ISagaStepStorage SagaStepStorage;
#pragma warning restore CA1051 // 不要声明可见实例字段
        private readonly ISagaNodeStorage sagaNodeStorage;

        #region TimeOutSetting

        public int DeadlineSeconds = 4;
        private DateTime deadTime;
        #endregion

        private ConcurrentDictionary<string, Func<CommandMessage, Task>> _methodList = new ConcurrentDictionary<string, Func<CommandMessage, Task>>();
        private ConcurrentDictionary<Type, Func<ISagaStepData, Task>> _funcList = new ConcurrentDictionary<Type, Func<ISagaStepData, Task>>();


        public BaseMainSaga(IBus bus, ISagaStepStorage sagaStepStorage, ISagaNodeStorage sagaNodeStorage) : this()
        {
            this.Bus = bus;
            this.sagaNodeStorage = sagaNodeStorage;
            this.SagaStepStorage = sagaStepStorage;
        }

        public BaseMainSaga()
        {
            var methods = this.GetType().GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) ??
                Enumerable.Empty<MethodInfo>();


            #region 利用Attribute来拦截
            foreach (var m in methods)
            {
                var attrib = Attribute.GetCustomAttribute(m, typeof(SagaActionAttribute), inherit: true);
                if (attrib == null)
                {
                    continue;
                }
                var actionAttrib = attrib as SagaActionAttribute;
                var actionName = actionAttrib.ReplyActionName;
                var methodname = m.Name;
                var parameter = Expression.Parameter(typeof(CommandMessage), "message");

                var result = Expression.Lambda<Func<CommandMessage, Task>>(
                    Expression.Call(Expression.Constant(this), GetHandleReplyMethod(methodname), parameter), parameter
                    ).Compile();

                _ = this._methodList.TryAdd(actionName, result);
            }
            #endregion

            #region 利用接口
            var implementedHandlerInterfaces = this.GetType().GetInterfaces()
                   .Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IHandleMessages<>));
            foreach (var handler in implementedHandlerInterfaces)
            {
                var genericType = handler.GetGenericArguments()[0];
                var methodInfo = this.GetType().GetMethod("Handle",
                    BindingFlags.Public | BindingFlags.Instance,
                    null, new Type[] { genericType }, null);

                var func = new Func<ISagaStepData, Task>(x =>
                {

                    var result = (Task)methodInfo.Invoke(this, new object[] { x });
                    return result;
                });
                _ = this._funcList.TryAdd(genericType, func);

            } 
            #endregion
            Console.WriteLine();

        }



        public override Task HandleReply(CommandMessage message)
        {
            if (message is null)
            {
                throw new ArgumentNullException(nameof(message));
            }

            ///Todo 增加check
            this.Data = Activator.CreateInstance(typeof(TSagaData), new[] { message.CorrelationID }) as TSagaData;

            if (!this._methodList.TryGetValue(message.Action, out var func))
            {
                //todo 自定义exception
                throw new Exception($"Saga中没有定义[{message.Action}]的HandleReply");
            }
            var sagaStepData = message.GetObject();

            if (!this._funcList.TryGetValue(sagaStepData.GetType(), out var func1))
            {
                //todo 自定义exception
                throw new Exception($"Saga中没有定义[{message.Action}]的HandleReply");
            }
            func1.Invoke((ISagaStepData)sagaStepData).GetAwaiter().GetResult();
            return func?.Invoke(message);
        }



        private MethodInfo GetHandleReplyMethod(string methodName)
        {
            var genericMethod = GetType().GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance)
                ?? throw new ArgumentException($"Could not find the {methodName} method?!");
            return genericMethod;
        }


        /// <summary>
        /// 设置超时时间
        /// </summary>
        /// <returns></returns>
        public DateTime SetTimeOut() => deadTime = DateTime.Now.AddSeconds(DeadlineSeconds);
        /// <summary>
        /// Saga是否超时
        /// </summary>
        /// <returns></returns>
        protected bool IsTimeOut() => DateTime.Now > deadTime;

        protected TSagaData Data { get; private set; }
        public IBus Bus { get; set; }

        private CommandMessage commandMessage;
        public void SendCommand() => this.Bus.SendCommand(this.commandMessage);
        public void SendCommand(string actionName, string topicName, string replyChannel, object instance)
        {
            this.commandMessage.Action = actionName;
            this.commandMessage.Source = topicName;
            this.commandMessage.ReplyChannel = replyChannel;
            this.commandMessage.SetObject(instance);
            this.commandMessage.RecordID = "";
            this.SendCommand();
        }

        public void SetParams(CommandMessage message, TSagaData sagaData)
        {
            this.commandMessage = message;
            this.Data = sagaData;
            this.SetTimeOut();
        }

        public CommandMessage GetCommandMessage() => this.commandMessage;

        public Task Rollback()
        {
            //回滚哪些node呢
            //只回滚已经做成功的
            Console.WriteLine($"进入回滚流程，是否因为超时{this.IsTimeOut()}");
            var toBeRolledBackNodes = this.sagaNodeStorage.FindBySagaId(this.Data.SagaId, Storages.Enums.SagaNodeState.SUCCESS);
            toBeRolledBackNodes?.ForEach(node =>
            {
                var commandmessage = node.CommandMessageData.ToObject<CommandMessage>();
                this.Bus.SendCommand(commandmessage);
            });
            return Task.CompletedTask;
        }
        public async Task<string> WaitSagaFinish()
        {

            return await Task.Run(async () =>
            {
                if (this.IsTimeOut())
                {
                    _ = this.Rollback();
                    return this.GetCommandMessage().CorrelationID;

                }
                if (this.Data.IsDone())
                {
                    return this.GetCommandMessage().CorrelationID;
                }
                Thread.Sleep(400);
                return await this.WaitSagaFinish().ConfigureAwait(false);

            }).ConfigureAwait(false);

        }

        public void SetChangedAction(object memberName, bool memberValue)

        {

        }


    }




}
